package vn.ugame.processor;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import vn.ugame.controller.UgameContext;

public class ProcessorManager {

    private static ProcessorManager instance;
    private List<Class> processorClasses;

    public static ProcessorManager getInstance() {
        if (instance == null) {
            instance = new ProcessorManager();
        }
        return instance;
    }

    private ProcessorManager() {
        processorClasses = getProcessorClasses();
    }

    public Processor createProcessor(String name) 
            throws InstantiationException, IllegalAccessException {
        for (Class c : processorClasses) {
            ServiceProcessor processor = (ServiceProcessor) c.getAnnotation(ServiceProcessor.class);
            if (processor.value().equalsIgnoreCase(name)) {
                return (Processor) c.newInstance();
            }
        }
        return null;
    }

    private List<Class> getAllClasses() {
        List<Class> allClasses = new ArrayList<Class>();

        String path = UgameContext.getServletContext().getRealPath("WEB-INF/classes");

        File file = new File(path);

        File[] files = file.listFiles();
        for (File f : files) {
            if (f.isDirectory()) {
                try {
                    allClasses.addAll(getClasses(f.getName()));
                } catch (ClassNotFoundException ex) {
                } catch (IOException ex) {
                }
            }
        }

        return allClasses;
    }

    private List<Class> getProcessorClasses() {
        List<Class> classes = new ArrayList<Class>();
        List<Class> allClasses = getAllClasses();
        for (Class c : allClasses) {
            ServiceProcessor processor = (ServiceProcessor) c.getAnnotation(ServiceProcessor.class);
            if (processor != null) {
                classes.add(c);
            }
        }
        return classes;
    }

    /**
     * Scans all classes accessible from the context class loader which belong
     * to the given package and subpackages.
     *
     * @param packageName The base package
     * @return The classes
     * @throws ClassNotFoundException
     * @throws IOException
     */
    private List<Class> getClasses(String packageName) throws ClassNotFoundException, IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        assert classLoader != null;
        String path = packageName.replace('.', '/');
        Enumeration<URL> resources = classLoader.getResources(path);
        List<File> dirs = new ArrayList<File>();
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            dirs.add(new File(resource.getFile()));
        }
        ArrayList<Class> classes = new ArrayList<Class>();
        for (File directory : dirs) {
            classes.addAll(findClasses(directory, packageName));
        }
        return classes;
    }

    /**
     * Recursive method used to find all classes in a given directory and
     * subdirs.
     *
     * @param directory The base directory
     * @param packageName The package name for classes found inside the base
     * directory
     * @return The classes
     * @throws ClassNotFoundException
     */
    private List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
        List<Class> classes = new ArrayList<Class>();
        if (!directory.exists()) {
            return classes;
        }
        File[] files = directory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                assert !file.getName().contains(".");
                classes.addAll(findClasses(file, packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
            }
        }
        return classes;
    }
}
